package jwt

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	jwtpkg "github.com/golang-jwt/jwt/v5"
	"go.uber.org/zap"
	"store/global"
	"strings"
	"time"
)

var (
	ErrTokenExpired           error = errors.New("令牌已过期")
	ErrTokenExpiredMaxRefresh error = errors.New("令牌已过最大刷新时间")
	ErrTokenMalformed         error = errors.New("请求令牌格式有误")
	ErrTokenInvalid           error = errors.New("请求令牌无效")
	ErrHeaderEmpty            error = errors.New("需要认证才能访问！")
	ErrHeaderMalformed        error = errors.New("请求头中 Authorization 格式有误")
)

// JWT 结构体
type JWT struct {
	SignKey []byte // 秘钥，用以加密jwt，读取配置信息 Http.JwtKey
}

// JwtClaims 自定义载荷
/*  JwtClaims 结构体实现了 Claims 接口继承了  Valid() 方法
JWT 规定了7个官方字段，提供使用:
- iss (issuer)：发布者
- sub (subject)：主题
- iat (Issued At)：生成签名的时间
- exp (expiration time)：签名过期时间
- aud (audience)：观众，相当于接受者
- nbf (Not Before)：生效时间
- jti (JWT ID)：编号
*/
type JwtClaims struct {
	UserID       uint64 `json:"user_id"`
	ExpireAtTime int64  `json:"expire_time"`
	IsFreshToken bool   `json:"is_fresh_token"`
	jwtpkg.RegisteredClaims
}

func NewJwt() *JWT {
	return &JWT{SignKey: []byte(global.Config.Http.JwtKey)}
}

// getToken 获取 Token 如 Authorization:Bearer xxxxx
func (jwt *JWT) getToken(c *gin.Context) (string, error) {
	// 优先从Header获取
	authHeader := c.Request.Header.Get("Authorization")
	if authHeader != "" {
		parts := strings.SplitN(authHeader, " ", 2)
		if len(parts) == 2 && parts[0] == "Bearer" {
			return parts[1], nil
		}
		return "", ErrHeaderMalformed
	}

	// 其次从URL参数获取（比如：?access_token=...）
	if authHeader = c.Query("access_token"); authHeader != "" {
		return authHeader, nil // 直接返回不带Bearer的token
	}

	return "", ErrHeaderEmpty
}

// createToken 创建 Token，内部使用，外部请调用 IssueToken
func (jwt *JWT) createToken(claims JwtClaims) (string, error) {
	// 使用HS256算法进行token生成
	token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
	return token.SignedString(jwt.SignKey)
}

// ParserToken 解析 Token，中间件中调用
func (jwt *JWT) ParserToken(c *gin.Context) (*JwtClaims, error) {
	tokenStr, errToken := jwt.getToken(c)
	if errToken != nil {
		return nil, errToken
	}
	// 1. 调用 jwt 库解析用户的 Token
	// 去除Bearer 前缀,获取后面的字符串
	tokenStr = strings.TrimPrefix(tokenStr, "Bearer ")
	token, err := jwtpkg.ParseWithClaims(
		tokenStr,
		&JwtClaims{},
		func(token *jwtpkg.Token) (interface{}, error) {
			return jwt.SignKey, nil
		},
	)

	// 2. 解析出错时处理
	if err != nil {
		if errors.Is(err, jwtpkg.ErrTokenMalformed) {
			return nil, ErrTokenMalformed
		} else if errors.Is(err, jwtpkg.ErrTokenExpired) {
			return nil, ErrTokenExpired
		}
		return nil, ErrTokenInvalid
	}

	// 3. 将 token 中的claims信息解析出来和 JwtClaims 数据结构进行校验
	if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, ErrTokenInvalid
}

// createTokenWithType 通用token创建方法
func (jwt *JWT) createTokenWithType(c *gin.Context, userID uint64, tokenType string) (string, int64) {
	var expireTime int
	isFresh := false

	switch tokenType {
	case "refresh":
		expireTime = int(global.Config.Http.JwtRefreshTokenExpire)
		isFresh = true
	default:
		expireTime = int(global.Config.Http.JwtAccessExpire)
	}

	expireAtTime := _timezone().Add(time.Duration(expireTime) * time.Second).Unix()
	now := jwtpkg.NewNumericDate(_timezone())

	claims := JwtClaims{
		userID,
		expireAtTime,
		isFresh,
		jwtpkg.RegisteredClaims{
			NotBefore: now,
			IssuedAt:  now,
			ExpiresAt: jwtpkg.NewNumericDate(time.Unix(expireAtTime, 0)),
			Issuer:    "hyholin",
		},
	}

	token, err := jwt.createToken(claims)
	if err != nil {
		logMsg := fmt.Sprintf("生成%s token失败", tokenType)
		global.Logger.Error(logMsg, zap.String("reqKey", c.GetString("reqKey")), zap.Error(err))
		return "", 0
	}
	return token, expireAtTime
}

func _timezone() time.Time {
	chinaTimezone, _ := time.LoadLocation(time.Local.String())
	return time.Now().In(chinaTimezone)
}

// IssueToken 主Token接口
func (jwt *JWT) IssueToken(c *gin.Context, userID uint64) (string, int64) {
	return jwt.createTokenWithType(c, userID, "access")
}

// IssueRefreshToken 刷新Token接口
func (jwt *JWT) IssueRefreshToken(c *gin.Context, userID uint64) (string, int64) {
	return jwt.createTokenWithType(c, userID, "refresh")
}
